package com.yuyou.tas.util.tool;

import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.StringRedisTemplate;

public class RedisLockUtil {

	private final static Logger logger = LoggerFactory.getLogger(RedisLockUtil.class);
	/**
	 * 单个锁有效期，秒
	 */
	private static final int DEFAULT_SINGLE_EXPIRE_TIME = 60;
	/**
	 * 等待锁轮循次数
	 */
	private static final int TRY_AGAIN_TIMES = 200;
	
	/**
	 * 释放锁 与tryLock(String,String)搭配使用
	 */
	public static void unLock(String key, String businessPrefix) {
		StringRedisTemplate stringRedisTemplate=(StringRedisTemplate)SpringUtil.getBean("stringRedisTemplate");
		key = businessPrefix + key;
		stringRedisTemplate.delete(key);
	}

	public static boolean tryLock(String key, String businessPrefix) {
		RedisConnection  rc=null;
		try {
			StringRedisTemplate stringRedisTemplate=(StringRedisTemplate)SpringUtil.getBean("stringRedisTemplate");
			 rc=stringRedisTemplate.getConnectionFactory().getConnection();
			// 系统计时器的当前值，以毫微秒为单位。
			long nano = System.currentTimeMillis();

			int tryTimes = 0;
			key = businessPrefix + key;
			logger.info("------------------lock key=:"+key);
			do {
				tryTimes++;
				Boolean b = rc.setNX(key.getBytes(), String.valueOf(nano).getBytes());
				if (b) {
					// 设置过期时间
					Boolean exRes=stringRedisTemplate.expire(key, DEFAULT_SINGLE_EXPIRE_TIME,
							TimeUnit.SECONDS);
					if(!exRes){
						stringRedisTemplate.delete(key);
						return Boolean.FALSE;
					}
					
					// 成功获取锁，返回true
					return Boolean.TRUE;
				} else {
					// 获取设置的毫微秒值，已判断，如果setnx后异常(服务挂了)期时间，导致死锁问题
					String currentValueStr = stringRedisTemplate.opsForValue()
							.get(key);

					// 如果存在锁且锁已超时，则重新设置锁
					if (StringUtils.isNotEmpty(currentValueStr)
							&& (nano - Long.valueOf(currentValueStr)) > TimeUnit.SECONDS.toMillis(DEFAULT_SINGLE_EXPIRE_TIME)) {
						// 判断是否为空，不为空的情况下，如果被其他线程设置了值，则第二个条件判断是过不去的
						byte[] oldValueStrByte = rc.getSet(key.getBytes(),
										String.valueOf(nano).getBytes());
						String oldValueStr = new String(oldValueStrByte);

						// 获取上一个锁到期时间，并设置现在的锁到期时间，
						// 只有一个线程才能获取上一个线上的设置时间，因为jedis.getSet是同步的
						if (oldValueStr != null
								&& oldValueStr.equals(currentValueStr)) {
							// 如过这个时候，多个线程恰好都到了这里，但是只有一个线程的设置值和当前值相同，他才有权利获取锁
							return Boolean.TRUE;
						}
					}

				}
				Thread.sleep(300);
			} while (tryTimes < TRY_AGAIN_TIMES);
			return Boolean.FALSE;
		} catch (Exception e) {
			LogUtil.error(logger, "", e);
		}finally{
			if(!rc.isClosed()){
				rc.close();
			}
		}
		return Boolean.FALSE;
	}

}
